#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from google.protobuf.message import DecodeError
from lib import AMessage_pb2
from exceptions import PBDataTypeError, PBDataParseError
from datetime import datetime

STAT_DATA_START = 1
STAT_DATA_CHUNK = 2
STAT_DATA_END = 3


class RawPBPack:
    """对原始的二进制数据包进行解析，如果该包不完整不能调用反序列化方法"""
    def __init__(self, raw_data, endian="little"):
        """
        解析原始二进制包
        :param raw_data: 源数据
        :param endian: 端序
        """
        self.raw_data = raw_data
        self.endian = endian
        self.from_header = self._from_header()
        self.length = self._length()
        self.typ = self._typ()
        self.pb_obj = None

    def _from_header(self):
        """包体1-2个字节是协议头，为\xf0\xf0"""
        s = slice(0, 2)
        header = self.raw_data[s]
        if header == b'\xf0\xf0':
            return True
        else:
            return False

    def _length(self):
        """包体3-6个字节是包长度"""
        s = slice(2, 6)
        length = self.raw_data[s]
        length = int.from_bytes(length, self.endian)
        return length

    def _typ(self):
        """包体7-10个字节是消息类型"""
        s = slice(6, 10)
        typ = self.raw_data[s]
        typ = int.from_bytes(typ, self.endian)
        return typ

    def _deserialize(self):
        """根据消息类型获取pb反序列化方式，然后返回pb对象"""
        if self.typ == STAT_DATA_START:
            pb_obj = AMessage_pb2.StatDataStart()
        elif self.typ == STAT_DATA_CHUNK:
            pb_obj = AMessage_pb2.StatDataChunk()
        elif self.typ == STAT_DATA_END:
            pb_obj = AMessage_pb2.StatDataEnd()
        else:
            raise PBDataTypeError("Invalid data type {}".format(self.typ))

        s = slice(10, self.length)
        raw_content = self.raw_data[s]
        try:
            pb_obj.ParseFromString(raw_content)
            self.pb_obj = pb_obj
        except DecodeError as e:
            raise PBDataParseError("{}: {}".format(e, raw_content))
        return pb_obj


class PBMsgHeader(RawPBPack):
    """对完整的PB Header Msg反序列化并解析，拼装新的数据结构"""
    def __init__(self, raw_data):
        super(PBMsgHeader, self).__init__(raw_data)
        self._deserialize()
        if self.typ != STAT_DATA_START:
            raise PBDataTypeError("Current data type {} is not for STAT_DATA_START".format(self.typ))
        self.group_name = self._group_name()
        self.user_name = self._user_name()
        self.datetime = self._datetime()

    def _group_name(self):
        """获取表头"""
        groupname = getattr(self.pb_obj, "groupname", "")
        return groupname.split(',')

    def _identifier(self):
        """获取客户端标识符"""
        user = getattr(self.pb_obj, "username", "")
        return user

    def _user_name(self):
        """获取用户"""
        result = self._identifier()
        t = result.split('_')
        user = ""
        if len(t) > 1:
            user = '_'.join(t[:-1])
        return user

    def _datetime(self):
        """获取发送时间"""
        result = self._identifier()
        t = result.split('_')
        dt = ""
        if len(t) > 1:
            dt = t[-1]
            dt = datetime.strptime(dt, "%Y.%m.%d-%H.%M.%S")
            dt = datetime.strftime(dt, "%Y-%m-%d_%H-%M-%S")
        return dt

    def get_header(self):
        """拼装新的header数据结构"""
        header = {
            "groupname": self.group_name,
            "username": self.user_name,
            "datetime": self.datetime,
        }
        return header


class PBMsgStats(RawPBPack):
    """对完整的PB Stats Msg反序列化并解析，拼装新的数据结构"""
    def __init__(self, raw_data):
        super(PBMsgStats, self).__init__(raw_data)
        self._deserialize()
        if self.typ != STAT_DATA_CHUNK:
            raise PBDataTypeError("Current data type {} is not for STAT_DATA_CHUNK".format(self.typ))

    @staticmethod
    def _target_frame(chunk):
        """获取当前帧"""
        tf = getattr(chunk, "targetframe", -1)
        return int(tf)

    @staticmethod
    def _timestamp(chunk):
        """获取时间戳"""
        ts = getattr(chunk, "time", 0)
        # influxdb 入库的时间戳字段需要精确到纳秒，而受到的时间戳只精确到毫秒
        return int(ts) * 1000000

    @staticmethod
    def _group_data(chunk):
        """获取统计数据"""
        data = getattr(chunk, "groupdata", "")
        data = data.replace(' ', '').split(',')
        data_list = []
        for i, d in enumerate(data):
            if i == 0:
                data_list.append(int(d))
            else:
                data_list.append(float(d))
        return data_list

    def get_stats(self):
        """拼装新的stats数据结构"""
        chunks = getattr(self.pb_obj, "datachunk", [])
        stats = []
        for chunk in chunks:
            stat = {
                "targetframe": self._target_frame(chunk),
                "groupdata": self._group_data(chunk),
                "timestamp": self._timestamp(chunk),
            }
            stats.append(stat)
        return stats


if __name__ == "__main__":
    pass
